package wallet

import (
	"context"
	"fmt"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	nhttp "net/http"
	"strings"
	"time"
	"wzgames/internal/api/bindo"
	"wzgames/internal/client"
	"wzgames/internal/constant"
	"wzgames/internal/gvalidator"
	"wzgames/internal/model/dwp"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/response"
	"wzgames/pkg/logger"
	"wzgames/pkg/server/http"
	"wzgames/pkg/server/http/types"
)

func RegisterRoute(pGroup *gin.RouterGroup) {
	bizGroup := pGroup.Group("wallet")
	http.MethodMapper(bizGroup, types.GET)(balanceFn())
	http.MethodMapper(bizGroup, types.POST)(depositFn())
	http.MethodMapper(bizGroup, types.POST)(withdrawFn())
	http.MethodMapper(bizGroup, types.POST)(transferCheckFn())
}

func checkDWParams(params bindo.DWBind) int16 {
	if len(params.AgtCode) > 15 || !gvalidator.IsAlphaNumeric(params.AgtCode) {
		return response.ErrBadRequest
	} else if len(params.AgtUser) > 0 && (!gvalidator.IsAgentUname(params.AgtUser) || len(params.AgtUser) > 64) {
		return response.ErrUsername
	} else if !gvalidator.IsAlphaNumeric(params.HashVal) || len(params.HashVal) > 64 {
		return response.ErrSign
	} else if !gvalidator.IsAgentUid(params.AgtUid) || len(params.AgtUid) > 64 {
		return response.ErrBadRequest
	} else if !gvalidator.IsAmount(params.Amount) {
		return response.ErrBadRequest
	} else if !gvalidator.IsAgentUid(params.AgtOrderId) || len(params.AgtOrderId) > 64 {
		return response.ErrBadRequest
	} else if !gvalidator.IsAlphaNumeric(params.ACurrency) || len(params.ACurrency) > 10 {
		return response.ErrBadRequest
	} else if !gvalidator.IsNumeric(params.Stamp) || !gvalidator.CheckTimestamp(params.Stamp) {
		return response.ErrBadRequest
	}

	return 0
}

var depositFn = func() (string, func(c *gin.Context)) {
	uri := fmt.Sprintf("/deposit")
	return uri, func(c *gin.Context) {
		resp := types.Response{}
		var params bindo.DWBind
		if err := c.ShouldBind(&params); err != nil {
			//logger.Error("err == ", zap.String("error", err.Error()))
			resp.Code = response.ErrBadRequest
			c.JSON(nhttp.StatusOK, resp)
		}
		resp.Code = checkDWParams(params)
		if resp.Code != 0 {
			c.JSON(nhttp.StatusOK, resp)
			return
		}

		agtId, err := gvalidator.DecodeAgentID(params.AgtCode)
		if err != nil {
			//logger.Error("err == ", zap.String("error", err.Error()))
			logger.Error("[API] wallet deposit fail on decode agent code", zap.String("AgtCode", params.AgtCode),
				zap.String("User", params.AgtUser), zap.String("Currency", params.ACurrency), zap.String("Amount", params.Amount), zap.String("TxId", params.AgtOrderId))
			resp.Code = response.ErrBadRequest
			c.JSON(nhttp.StatusOK, resp)
			return
		}

		req := &dwp.DepWit{
			AgtID:       agtId,
			AgtCode:     params.AgtCode,
			AgtUsername: params.AgtUser,
			AgtUserId:   params.AgtUid,
			Amount:      params.Amount,
			AgtOrderId:  params.AgtOrderId,
			ACurrency:   params.ACurrency,
			Stamp:       params.Stamp,
			Sign:        params.HashVal,
			ClientIP:    c.ClientIP(),
		}

		reply := &dwp.DepWitAuthReply{}
		rpcCtx, cancelFn := context.WithTimeout(context.Background(), 15*time.Second)
		err = client.GetCatClient().Call(rpcCtx, "DepWitAuth", req, reply)
		cancelFn()
		if err != nil {
			resp.Code = response.ErrInternal
			logger.Error("[API] wallet deposit fail", zap.String("AgtCode", params.AgtCode), zap.String("User", params.AgtUser),
				zap.String("Currency", params.ACurrency), zap.String("Amount", params.Amount), zap.String("TxId", params.AgtOrderId), zap.Error(err))
		} else {
			resp.Code = int16(reply.Code)
			if reply.Code == 0 {
				depositReply := &dwp.DepWitReply{}
				rpcCtx, cancelFn = context.WithTimeout(context.Background(), 15*time.Second)
				err = client.GetDouWalletClient().Call(rpcCtx, "Deposit", reply, depositReply)
				cancelFn()
				if err != nil {
					resp.Code = response.ErrInternal
					logger.Error("[API] wallet deposit fail on dou wallet", zap.String("AgtCode", params.AgtCode),
						zap.String("User", params.AgtUser), zap.String("Currency", params.ACurrency), zap.String("Amount", params.Amount), zap.String("TxId", params.AgtOrderId), zap.Error(err))
				} else {
					resp.Code = int16(depositReply.Code)
					if resp.Code == 0 {
						resp.Code = 200
						resp.Data = depositReply.Data
					}
				}
			}
		}

		c.JSON(nhttp.StatusOK, resp)
		return
	}
}

var withdrawFn = func() (string, func(c *gin.Context)) {
	uri := fmt.Sprintf("/withdraw")
	return uri, func(c *gin.Context) {
		resp := types.Response{}
		var params bindo.DWBind
		if err := c.ShouldBind(&params); err != nil {
			//logger.Error("err == ", zap.String("error", err.Error()))
			resp.Code = response.ErrBadRequest
			c.JSON(nhttp.StatusOK, resp)
		}
		resp.Code = checkDWParams(params)
		if resp.Code != 0 {
			c.JSON(nhttp.StatusOK, resp)
			return
		}

		agtId, err := gvalidator.DecodeAgentID(params.AgtCode)
		if err != nil {
			logger.Error("err == ", zap.String("error", err.Error()))
			logger.Error("[API] wallet withdraw fail on decode agent code", zap.String("AgtCode", params.AgtCode),
				zap.String("User", params.AgtUser), zap.String("Currency", params.ACurrency), zap.String("Amount", params.Amount), zap.String("TxId", params.AgtOrderId))
			resp.Code = response.ErrBadRequest
			c.JSON(nhttp.StatusOK, resp)
			return
		}

		req := &dwp.DepWit{
			AgtID:       agtId,
			AgtCode:     params.AgtCode,
			AgtUsername: params.AgtUser,
			AgtUserId:   params.AgtUid,
			Amount:      params.Amount,
			AgtOrderId:  params.AgtOrderId,
			ACurrency:   params.ACurrency,
			Stamp:       params.Stamp,
			Sign:        params.HashVal,
			ClientIP:    c.ClientIP(),
		}

		reply := &dwp.DepWitAuthReply{}
		rpcCtx, cancelFn := context.WithTimeout(context.Background(), 15*time.Second)
		err = client.GetCatClient().Call(rpcCtx, "DepWitAuth", req, reply)
		cancelFn()
		if err != nil {
			resp.Code = response.ErrInternal
			logger.Error("[API] wallet withdraw fail", zap.String("AgtCode", params.AgtCode), zap.String("User", params.AgtUser),
				zap.String("Currency", params.ACurrency), zap.String("Amount", params.Amount), zap.String("TxId", params.AgtOrderId), zap.Error(err))
		} else {
			resp.Code = int16(reply.Code)
			if reply.Code == 0 {
				withdrawReply := &dwp.DepWitReply{}
				rpcCtx, cancelFn = context.WithTimeout(context.Background(), 15*time.Second)
				err = client.GetDouWalletClient().Call(rpcCtx, "Withdraw", reply, withdrawReply)
				cancelFn()
				if err != nil {
					resp.Code = response.ErrInternal
					logger.Error("[API] wallet withdraw fail on dou wallet", zap.String("AgtCode", params.AgtCode),
						zap.String("User", params.AgtUser), zap.String("Currency", params.ACurrency), zap.String("Amount", params.Amount), zap.String("TxId", params.AgtOrderId), zap.Error(err))
				} else {
					resp.Code = int16(withdrawReply.Code)
					if resp.Code == 0 {
						resp.Code = 200
						resp.Data = withdrawReply.Data
					}
				}
			}
		}

		c.JSON(nhttp.StatusOK, resp)
		return
	}
}

var balanceFn = func() (string, func(c *gin.Context)) {
	uri := fmt.Sprintf("/balance")
	return uri, func(c *gin.Context) {
		resp := types.Response{}
		var params bindo.BalanceBind
		if err := c.ShouldBind(&params); err != nil {
			logger.Error("err == ", zap.String("error", err.Error()))
			resp.Code = response.ErrBadRequest
			c.JSON(nhttp.StatusOK, resp)
		}

		if len(params.AgtCode) > 15 || !gvalidator.IsAlphaNumeric(params.AgtCode) {
			resp.Code = response.ErrBadRequest
		} else if len(params.AgtUser) > 0 && (!gvalidator.IsAgentUname(params.AgtUser) || len(params.AgtUser) > 64) {
			resp.Code = response.ErrUsername
		} else if !gvalidator.IsAgentUid(params.AgtUid) || len(params.AgtUid) > 64 {
			resp.Code = response.ErrBadRequest
		} else if !gvalidator.IsAlphaNumeric(params.HashVal) || len(params.HashVal) > 64 {
			resp.Code = response.ErrSign
		} else if !gvalidator.IsNumeric(params.Stamp) || !gvalidator.CheckTimestamp(params.Stamp) {
			resp.Code = response.ErrBadRequest
		} else if !gvalidator.IsAlphaNumeric(params.ACurrency) || len(params.ACurrency) > 10 {
			resp.Code = response.ErrBadRequest
		}

		if resp.Code != 0 {
			c.JSON(nhttp.StatusOK, resp)
			return
		}

		agtId, err := gvalidator.DecodeAgentID(params.AgtCode)
		if err != nil {
			logger.Error("err == ", zap.String("error", err.Error()))
			logger.Error("[API] wallet balance fail on decode agent code", zap.String("AgtCode", params.AgtCode), zap.String("User", params.AgtUser), zap.String("Currency", params.ACurrency))
			resp.Code = response.ErrBadRequest
			c.JSON(nhttp.StatusOK, resp)
			return
		}

		req := &dwp.BalanceAuth{
			AgtID:       agtId,
			AgtCode:     params.AgtCode,
			AgtUsername: params.AgtUser,
			AgtUserId:   params.AgtUid,
			ACurrency:   params.ACurrency,
			Stamp:       params.Stamp,
			Sign:        params.HashVal,
			ClientIP:    c.ClientIP(),
		}

		reply := &dwp.BalanceAuthReply{}
		rpcCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
		err = client.GetCatClient().Call(rpcCtx, "BalanceAuth", req, reply)
		cancelFn()
		if err != nil {
			logger.Error("err == ", zap.String("error", err.Error()))
			resp.Code = response.ErrInternal
			logger.Error("[API] wallet balance fail", zap.String("AgtCode", params.AgtCode), zap.String("User", params.AgtUser), zap.String("Currency", params.ACurrency), zap.Error(err))
		} else {
			resp.Code = int16(reply.Code)
			if reply.Code == 0 {
				if reply.UserExists {
					balanceReply := &dwp.BalanceReply{}
					rpcCtx, cancelFn = context.WithTimeout(context.Background(), 10*time.Second)
					err = client.GetDouWalletClient().Call(rpcCtx, "Balance", reply, balanceReply)
					cancelFn()
					if err != nil {
						logger.Error("err == ", zap.String("error", err.Error()))
						resp.Code = response.ErrInternal
						logger.Error("[API] wallet balance fail on dou wallet", zap.String("AgtCode", params.AgtCode), zap.String("User", params.AgtUser), zap.String("Currency", params.ACurrency), zap.Error(err))
					} else {
						resp.Code = int16(balanceReply.Code)
						if balanceReply.Code == 0 {
							resp.Data = balanceReply.Data
						}
					}
				} else {
					resp.Data = &dwp.BalanceData{
						Uid:      reply.AgtUid,
						Username: reply.Uname,
						Balance:  "0",
						Currency: reply.Curr,
					}
				}
			}
		}
		if resp.Code == 0 {
			resp.Code = 200
		}

		c.JSON(nhttp.StatusOK, resp)
		return
	}
}

var transferCheckFn = func() (string, func(c *gin.Context)) {
	uri := fmt.Sprintf("/transfer_check")
	return uri, func(c *gin.Context) {
		//logger.Debug("request == ", zap.String("uri", c.Request.RequestURI), zap.String("host", c.Request.Host))

		resp := types.Response{}
		var params bindo.TransferBind
		if err := c.ShouldBind(&params); err != nil {
			logger.Error("err == ", zap.String("error", err.Error()))
			resp.Code = response.ErrBadRequest
			c.JSON(nhttp.StatusOK, resp)
		}

		if len(params.AgtCode) > 15 || !gvalidator.IsAlphaNumeric(params.AgtCode) {
			resp.Code = response.ErrBadRequest
		} else if !gvalidator.IsAlphaNumeric(params.HashVal) || len(params.HashVal) > 64 {
			resp.Code = response.ErrSign
		} else if !gvalidator.IsNumeric(params.Stamp) || !gvalidator.CheckTimestamp(params.Stamp) {
			resp.Code = response.ErrBadRequest
		} else if params.ACurrency != "" && !gvalidator.IsAlphaNumeric(params.ACurrency) || len(params.ACurrency) > 10 {
			resp.Code = response.ErrBadRequest
		} else if !gvalidator.IsAgentUid(params.AgtOrderId) || len(params.AgtOrderId) > 64 {
			resp.Code = response.ErrBadRequest
		} else if params.DorW != constant.KeyDeposit && params.DorW != constant.KeyWithdraw {
			resp.Code = response.ErrBadRequest
		}

		if resp.Code != 0 {
			c.JSON(nhttp.StatusOK, resp)
			return
		}

		agtId, err := gvalidator.DecodeAgentID(params.AgtCode)
		if err != nil {
			logger.Error("[API] wallet transfer check fail on decode agent code", zap.String("AgtCode", params.AgtCode), zap.String("AgtOrder", params.AgtOrderId), zap.String("Currency", params.ACurrency))
			resp.Code = response.ErrBadRequest
			c.JSON(nhttp.StatusOK, resp)
			return
		}

		var si strings.Builder
		si.WriteString(params.AgtCode)
		si.WriteString(params.AgtOrderId)
		si.WriteString(params.ACurrency)
		si.WriteString(params.DorW)
		si.WriteString(params.Stamp)
		req := &mcomm.NormalSignCheck{
			AgtID:     agtId,
			Combine:   si.String(),
			ACurrency: params.ACurrency,
			Sign:      params.HashVal,
			ClientIP:  c.ClientIP(),
		}
		reply := &mcomm.CodeRet{}
		rpcCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
		err = client.GetCatClient().Call(rpcCtx, "NormalSignAuth", req, reply)
		cancelFn()
		if err != nil {
			logger.Error("err == ", zap.String("error", err.Error()))
			resp.Code = response.ErrInternal
			logger.Error("[API] wallet transfer check fail", zap.String("AgtCode", params.AgtCode), zap.String("AgtOrder", params.AgtOrderId), zap.String("Currency", params.ACurrency), zap.Error(err))
		} else {
			resp.Code = int16(reply.Code)
			if reply.Code == 0 {
				dwCheck := &dwp.DwCheck{
					AgtID:      agtId,
					AgtCode:    params.AgtCode,
					AgtOrderId: params.AgtOrderId,
					DorW:       params.DorW,
					ACurrency:  params.ACurrency,
					Stamp:      params.Stamp,
					Sign:       params.HashVal,
					ClientIP:   c.ClientIP(),
				}
				checkReply := &dwp.DepWitReply{}
				rpcCtx, cancelFn = context.WithTimeout(context.Background(), 15*time.Second)
				err = client.GetDouWalletClient().Call(rpcCtx, "CheckTransfer", dwCheck, checkReply)
				cancelFn()
				if err != nil {
					logger.Error("err == ", zap.String("error", err.Error()))
					resp.Code = response.ErrInternal
					logger.Error("[API] wallet transfer check fail on dou wallet", zap.String("AgtCode", params.AgtCode), zap.String("AgtOrder", params.AgtOrderId), zap.String("Currency", params.ACurrency), zap.Error(err))
				} else {
					resp.Code = int16(checkReply.Code)
					if checkReply.Code == 0 {
						resp.Data = checkReply.Data
					}
				}
			}
		}
		if resp.Code == 0 {
			resp.Code = 200
		}

		c.JSON(nhttp.StatusOK, resp)
		return
	}
}
